package com.nulldev.util.threading.v2;

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import com.nulldev.util.java.LargeLinkedList;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.math.lNumber;

public class ThreadManager extends Thread {

	private final LargeLinkedList<nThread> tasks = new LargeLinkedList<nThread>();
	private ExecutorService executor;
	private boolean singleExecutionMode;
	private final ExecutorService specExec = Executors.newSingleThreadExecutor(new ThreadFactory() {

		@Override
		public Thread newThread(Runnable r) {
			return new Thread(r, "nullUtil: nThread Exec Thread [ThreadExec.Type_SINGLE_GL]");
		}
	});

	private final Logger log = LoggerFactory.getLogger();

	public ThreadManager() {
		this(Executors.newCachedThreadPool(new ThreadFactory() {

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "nullUtil: nThread Exec Thread");
			}
		}));
	}

	public ThreadManager(final boolean openGL) {
		this(openGL ? Executors.newSingleThreadExecutor(new ThreadFactory() {

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "nullUtil: nThread Exec Thread");
			}
		}) : Executors.newCachedThreadPool(new ThreadFactory() {

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "nullUtil: nThread Exec Thread");
			}
		}));
	}
	
	public ThreadManager(final boolean openGL, final boolean singleExecution) {
		this(openGL ? Executors.newSingleThreadExecutor(new ThreadFactory() {

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "nullUtil: nThread Exec Thread");
			}
		}) : Executors.newCachedThreadPool(new ThreadFactory() {

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "nullUtil: nThread Exec Thread");
			}
		}));
		this.singleExecutionMode = singleExecution;
	}

	public ThreadManager(final ExecutorService executor) {
		super("nullUtil: nThread Executor");
		if (executor == null) {
			System.out.println("nullUtil @ ThreadManager: Variable 'executor' was null, defaulting...");
			this.executor = Executors.newCachedThreadPool(new ThreadFactory() {

				@Override
				public Thread newThread(Runnable r) {
					return new Thread(r, "nullUtil: nThread Exec Thread");
				}
			});
		}else {
			this.executor = executor;
		}
		this.log.debugEnabled = false;
		this.start();
	}

	public boolean addTask(final nThread task) {
		return tasks.add(task);
	}
	
	@Deprecated
	public boolean isSingleExecutionMode() {
		return this.singleExecutionMode;
	}
	
	@Deprecated
	public void setSingleExecutionMode(final boolean value) {
		this.singleExecutionMode = value;
	}

	@Override
	public void run() {
		final LinkedList<nThread> delayed = new LinkedList<nThread>();
		while (true) {
			try {
				if (this.tasks.size() > 0) {
					log.debug("[ThreadManager] Tasks remaining " + (this.tasks.size() - 1) + ".");
					final nThread task = this.tasks.removeFirst();
					if (task == null) {
						continue;
					}
					if (task.priority() == ThreadPriority.DELAYABLE) {
						if (this.tasks.size() == 1) {
							log.debug("[ThreadManager] Running delayed task '" + task.id() + "'...");
							this.executor.execute(new Runnable() {
								
								@Override
								public void run() {
									try {
										task.run();
									}catch (Exception e) {}
								}
							});
						}else {
							log.debug("[ThreadManager] Delaying task '" + task.id() + "'...");
							delayed.add(task);
						}
					}else {
						log.debug("[ThreadManager] Running task '" + task.id() + "'...");
						this.executor.execute(new Runnable() {
							
							@Override
							public void run() {
								try {
									task.run();
								}catch (Exception e) {}
							}
						});
					}
				}else if (delayed.size() > 0) {
					final nThread task = delayed.removeFirst();
					log.debug("[ThreadManager] Running delayed task '" + task.id() + "'...");
					this.executor.execute(new Runnable() {
						
						@Override
						public void run() {
							task.run();
						}
					});
				}
				if (this.tasks.size() == 0 && delayed.size() == 0) {
					try {
						Thread.sleep(5L);
					} catch (InterruptedException e) {
					}
				}else {
					try {
						Thread.sleep(this.delay);
					} catch (InterruptedException e) {
					}
				}
			}catch (Exception e) {
				if (!(e instanceof NoSuchElementException)) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/*
	 * 
	 * 			final LargeLinkedList<nThread> toExecute = (LargeLinkedList<nThread>) tasks.clone();
			final LargeLinkedList<nThread> delayed = new LargeLinkedList<nThread>();
			if (this.singleExecutionMode) {
				for (int i = 0; i < toExecute.sizel(); i++) {
					final int ic = i;
					final nThread task = toExecute.get(i);
					if (task.priority() == ThreadPriority.DELAYABLE) {
						log.debug("[ThreadManager] Delaying task '" + task.id() + "'...");
						task.setExecuted(false);
						delayed.add(task);
					}else {
						if (task.mode() == ThreadMode.NORMAL && !task.done()) {
							ThreadManager.this.executor.execute(new Runnable() {

								@Override
								public void run() {
									log.debug("[ThreadManager] Running task '" + task.id() + "'...");
									task.run();
								}
							});
						}else if (!task.done()) {
							ThreadManager.this.specExec.execute(new Runnable() {

								@Override
								public void run() {
									log.debug("[ThreadManager] Running task '" + task.id() + "'...");
									task.run();
								}
							});
						}
						while (!task.done()) {
							try {
								Thread.sleep(1L);
							} catch (InterruptedException e) {
							}
						}
						log.debug("[ThreadManager] Task '" + task.id() + "' complete.");
						if (task.execution() != ThreadExecution.LOOPED) {
							log.debug("[ThreadManager] Task '" + task.id() + "' removed.");
							try {
								ThreadManager.this.tasks.remove(ic);
							}catch (Exception e) {
								try {
									ThreadManager.this.tasks.remove(ic - 1);
								}catch (Exception e1) {}
							}
						}
					}
				}
				for (int i = 0; i < delayed.sizel(); i++) {
					final int ic = i;
					final nThread task = toExecute.get(ic);
					if (task.mode() == ThreadMode.OPENGL_SINGLE && !task.done()) {
						ThreadManager.this.specExec.execute(new Runnable() {

							@Override
							public void run() {
								log.debug("[ThreadManager] Running delayed task '" + task.id() + "'...");
								task.run();
							}
						});
					}else if (!task.done()) {
						ThreadManager.this.executor.execute(new Runnable() {

							@Override
							public void run() {
								log.debug("[ThreadManager] Running delayed task '" + task.id() + "'...");
								task.run();
							}
						});
					}
					while (!task.done()) {
						try {
							Thread.sleep(1L);
						} catch (InterruptedException e) {
						}
					}
					log.debug("[ThreadManager] Delayed task '" + task.id() + "' complete.");
					if (task.execution() != ThreadExecution.LOOPED) {
						log.debug("[ThreadManager] Delayed Task '" + task.id() + "' removed.");
						if (ic >= ThreadManager.this.tasks.size()) {
							try {
								ThreadManager.this.tasks.remove(ic);
							}catch (Exception e) {
								try {
									ThreadManager.this.tasks.remove(ic - 1);
								}catch (Exception e1) {
								}
							}
						}else {
							ThreadManager.this.tasks.remove(ic);
						}
					}
				}
			}else {
				for (int i = 0; i < toExecute.sizel(); i++) {
					final int ic = i;
					final nThread task = toExecute.get(i);
					if (task != null) {
						if (!task.isExecuted()) {
							this.executor.execute(new Runnable() {
								
								@Override
								public void run() {
									task.setExecuted(true);
									if (task.priority() == ThreadPriority.DELAYABLE) {
										log.debug("[ThreadManager] Delaying task '" + task.id() + "'...");
										task.setExecuted(false);
										delayed.add(task);
									}else {
										if (task.mode() == ThreadMode.NORMAL && !task.done()) {
											ThreadManager.this.executor.execute(new Runnable() {

												@Override
												public void run() {
													log.debug("[ThreadManager] Running task '" + task.id() + "'...");
													task.run();
												}
											});
										}else if (!task.done()) {
											ThreadManager.this.specExec.execute(new Runnable() {

												@Override
												public void run() {
													log.debug("[ThreadManager] Running task '" + task.id() + "'...");
													task.run();
												}
											});
										}
										while (!task.done()) {
											try {
												Thread.sleep(1L);
											} catch (InterruptedException e) {
											}
										}
										log.debug("[ThreadManager] Task '" + task.id() + "' complete.");
										if (task.execution() != ThreadExecution.LOOPED) {
											log.debug("[ThreadManager] Task '" + task.id() + "' removed.");
											try {
												ThreadManager.this.tasks.remove(ic);
											}catch (Exception e) {
												try {
													ThreadManager.this.tasks.remove(ic - 1);
												}catch (Exception e1) {}
											}
										}
									}
								}
							});
						}
					}
				}
				for (int i = 0; i < delayed.sizel(); i++) {
					final int ic = i;
					final nThread task = toExecute.get(ic);
					if (!task.isExecuted()) {
						this.executor.execute(new Runnable() {
							
							@Override
							public void run() {
								task.setExecuted(true);
								if (task.mode() == ThreadMode.OPENGL_SINGLE && !task.done()) {
									ThreadManager.this.specExec.execute(new Runnable() {

										@Override
										public void run() {
											log.debug("[ThreadManager] Running delayed task '" + task.id() + "'...");
											task.run();
										}
									});
								}else if (!task.done()) {
									ThreadManager.this.executor.execute(new Runnable() {

										@Override
										public void run() {
											log.debug("[ThreadManager] Running delayed task '" + task.id() + "'...");
											task.run();
										}
									});
								}
								while (!task.done()) {
									try {
										Thread.sleep(1L);
									} catch (InterruptedException e) {
									}
								}
								log.debug("[ThreadManager] Delayed task '" + task.id() + "' complete.");
								if (task.execution() != ThreadExecution.LOOPED) {
									log.debug("[ThreadManager] Delayed Task '" + task.id() + "' removed.");
									if (ic >= ThreadManager.this.tasks.size()) {
										try {
											ThreadManager.this.tasks.remove(ic);
										}catch (Exception e) {
											try {
												ThreadManager.this.tasks.remove(ic - 1);
											}catch (Exception e1) {
											}
										}
									}else {
										ThreadManager.this.tasks.remove(ic);
									}
								}
							}
						});
					}
				}
			}
	 * 
	 */

	public Logger getLogger() {
		return this.log;
	}

	public void clear() {
		this.tasks.clear();
	}

	private int limit = 0;
	public boolean isFull() {
		if (limit <= 0) {
			return false;
		}
		return this.tasks.sizeL().moreThan(this.limit);
	}
	
	public int getLimit() {
		return this.limit;
	}
	
	public void setLimit(final int limit) {
		this.limit = limit;
	}

	public lNumber getQueue() {
		return this.tasks.sizeL();
	}

	private long delay = 1L;
	public void setDelay(final long delay) {
		this.delay = MathUtil.minClampL(1L, delay);
	}

	public void kill() {
		this.tasks.clear();
	}
}
